using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using Dida.Tool;

namespace Dida
{
	public class StateMachineDOM<GlueTrait> : DOMObj
		where GlueTrait : StateGlueTrait
	{
		DOMNamespace nameSpace = new DOMNamespace();
		StateDOMCollection<GlueTrait> states;
		StateDOM<GlueTrait> initialState = null;
		TransitionDOMCollection<GlueTrait> transitions;

		public StateMachineDOM(string name)
			: base(name)
		{
			states = new StateDOMCollection<GlueTrait>(nameSpace);
			transitions = new TransitionDOMCollection<GlueTrait>(nameSpace);

			states.ItemAdded += OnStateAdded;
			states.ItemRemoved += OnStateRemoved;

			transitions.ItemAdded += OnTransitionAdded;
			transitions.ItemRemoved += OnTransitionRemoved;
			transitions.ItemNameChanged += OnTransitionNameChanged;
		}

		public StateDOMCollection<GlueTrait> States
		{
			get { return states; }
		}

		public TransitionDOMCollection<GlueTrait> Transitions
		{
			get { return transitions; }
		}

		private void OnStateAdded(object sender, ItemAddedArgs<StateDOM<GlueTrait>> args)
		{
			args.NewItem.StateMachine = this;
		}

		private void OnStateRemoved(object sender, ItemRemovedArgs args)
		{
			if (args.RemovedItem == initialState)
				initialState = null;
		}

		private void OnTransitionAdded(object sender, ItemAddedArgs<StateTransitionDOM<GlueTrait>> args)
		{
			StateTransitionDOM<GlueTrait> transition = args.NewItem;
			Debug.Assert(states.Contains(transition.From));
			Debug.Assert(states.Contains(transition.To));

			transition.From.OnExitTransitionAdded(transition);
		}

		private void OnTransitionRemoved(object sender, ItemRemovedArgs<StateTransitionDOM<GlueTrait>> args)
		{
			StateTransitionDOM<GlueTrait> transition = args.RemovedItem;
			transition.From.OnExitTransitionRemoved(args.Index);
		}

		private void OnTransitionNameChanged(object sender, ItemNameChangedArgs<StateTransitionDOM<GlueTrait>> args)
		{
			StateTransitionDOM<GlueTrait> transition = args.Item;
			transition.From.OnExitTransitionNameChanged(transition, args.OldName, args.NewName);
		}

		public StateDOM<GlueTrait> InitialState
		{
			get { return initialState; }
			set 
			{
				Debug.Assert(states.IndexOf(value) != -1,
					"This state isn't a part of this state machine");

				// if the new initial state is the same as the current one, just return
				if (initialState == value)
					return;

				StateDOM<GlueTrait> old = initialState;
				initialState = value;

				if (old != null)
					old.FireIsInitialStateChanged();
				if (value != null)
					value.FireIsInitialStateChanged();

				InitialStateChangedHandler iscHandler = InitialStateChanged;
				if (iscHandler != null)
					iscHandler(this, new InitialStateChangedArgs(old,value));
			}
		}

		public class InitialStateChangedArgs : EventArgs
		{
			StateDOM<GlueTrait> oldInitialState;
			StateDOM<GlueTrait> newInitialState;

			public InitialStateChangedArgs(StateDOM<GlueTrait> oldInitialState,
				StateDOM<GlueTrait> newInitialState)
			{
				this.oldInitialState = oldInitialState;
				this.newInitialState = newInitialState;
			}

			public StateDOM<GlueTrait> OldInitialState
			{
				get { return oldInitialState; }
			}

			public StateDOM<GlueTrait> NewInitialState
			{
				get { return newInitialState; }
			}
		}
		public delegate void InitialStateChangedHandler(object sender,InitialStateChangedArgs args);
		public event InitialStateChangedHandler InitialStateChanged;
	}

	public class StateDOMCollection<GlueTrait> : DOMCollection<StateDOM<GlueTrait>>
		where GlueTrait : StateGlueTrait
	{
		internal StateDOMCollection(DOMNamespace nameSpace)
			: base(nameSpace)
		{
		}

		public StateDOM<GlueTrait> Add(string name)
		{
			return Add(new StateDOM<GlueTrait>(name));
		}
	}

	public class TransitionDOMCollection<GlueTrait> : DOMCollection<StateTransitionDOM<GlueTrait>>
		where GlueTrait : StateGlueTrait
	{
		DOMNamespace nameSpace;

		internal TransitionDOMCollection(DOMNamespace nameSpace)
		{
			this.nameSpace = nameSpace;
		}

		public StateTraitDOM<GlueTrait> Add(Type type,
			StateDOM<GlueTrait> from, StateDOM<GlueTrait> to)
		{
			return Add(new StateTransitionDOM<GlueTrait>(
				nameSpace.CreateUniqueName(type.FullName),type,from,to));
		}
	}

	public class StateDOM<GlueTrait> : DOMObj
		where GlueTrait : StateGlueTrait
	{
		StateMachineDOM<GlueTrait> stateMachine;

		DOMNamespace nameSpace = new DOMNamespace();

		TraitCollection traits;
		StateTraitDOM<GlueTrait> glueTrait;

		TraitMethodResolver resolver = new TraitMethodResolver();

		TransitionsCollection<GlueTrait> entryTransitions = new TransitionsCollection<GlueTrait>();
		TransitionsCollection<GlueTrait> exitTransitions = new TransitionsCollection<GlueTrait>();

		public StateDOM(string name)
			: base(name)
		{
			nameSpace = new DOMNamespace();
			traits = new TraitCollection(this,nameSpace);
			traits.ItemAdded += OnTraitAdded;
			traits.ItemRemoved += OnTraitRemoved;
			traits.ItemNameChanged += OnTraitNameChanged;

			glueTrait = new StateTraitDOM<GlueTrait>("Glue", typeof(GlueTrait));
			traits.Add(glueTrait);
		}

		public StateMachineDOM<GlueTrait> StateMachine
		{
			get { return stateMachine; }
			internal set { stateMachine = value; }
		}

		private void OnTraitAdded(object sender, ItemAddedArgs args)
		{
			if (args.NewItem.Name.StartsWith("ExitTransition_") ||
				args.NewItem.Name.StartsWith("EntryTransition_"))
			{
				throw new Exception("The name of a normal trait (ie. non transition) shouldn't start with ExitTransition_ or EntryTransition_");
			}

			StateTraitDOM<GlueTrait> trait = (StateTraitDOM<GlueTrait>)args.NewItem;
			resolver.AddTrait(trait.Name,trait.TraitType,trait);

			if (Changed != null)
				Changed(this, new EventArgs());
		}

		private void OnTraitRemoved(object sender, ItemRemovedArgs args)
		{
			StateTraitDOM<GlueTrait> trait = (StateTraitDOM<GlueTrait>)args.RemovedItem;
			resolver.RemoveTrait(trait.Name);

			if (Changed != null)
				Changed(this, new EventArgs());
		}

		private void OnTraitNameChanged(object sender, ItemNameChangedArgs args)
		{
			resolver.RemoveTrait(args.OldName);

			StateTraitDOM<GlueTrait> trait = (StateTraitDOM<GlueTrait>)args.Item;
			resolver.AddTrait(trait.Name, trait.TraitType,trait);

			if (Changed != null)
				Changed(this, new EventArgs());
		}

		internal void OnExitTransitionAdded(StateTransitionDOM<GlueTrait> transition)
		{
			exitTransitions.OnAdded(transition);
			resolver.AddTrait(transition.Name, transition.TraitType, transition);

			if (Changed != null)
				Changed(this, new EventArgs());
		}

		internal void OnExitTransitionRemoved(int index)
		{
			resolver.RemoveTrait(exitTransitions[index].Name);
			exitTransitions.OnRemoved(index);		

			if (Changed != null)
				Changed(this, new EventArgs());
		}

		internal void OnExitTransitionNameChanged(StateTransitionDOM<GlueTrait> transition,
			string oldName, string newName)
		{
			resolver.RemoveTrait(oldName);
			resolver.AddTrait(newName, transition.TraitType, transition);

			if (Changed != null)
				Changed(this, new EventArgs());
		}

		internal void OnEntryTransitionAdded(StateTransitionDOM<GlueTrait> transition)
		{
			entryTransitions.OnAdded(transition);
			resolver.AddTrait(transition.Name, transition.TraitType, transition);

			if (Changed != null)
				Changed(this, new EventArgs());
		}

		internal void OnEntryTransitionRemoved(int index)
		{
			resolver.RemoveTrait(entryTransitions[index].Name);
			entryTransitions.OnRemoved(index);

			if (Changed != null)
				Changed(this, new EventArgs());
		}

		internal void OnEntryTransitionNameChanged(StateTransitionDOM<GlueTrait> transition,
			string oldName, string newName)
		{
			resolver.RemoveTrait(oldName);
			resolver.AddTrait(newName, transition.TraitType, transition);

			if (Changed != null)
				Changed(this, new EventArgs());
		}


		public TraitCollection Traits
		{
			get { return traits; }
		}

		public TransitionsCollection<GlueTrait> EntryTransitions
		{
			get { return entryTransitions; }
		}

		public TransitionsCollection<GlueTrait> ExitTransitions
		{
			get { return exitTransitions; }
		}

		public StateTraitDOM<GlueTrait> Glue
		{
			get { return glueTrait; }
		}

		public event EventHandler Changed;

		public TraitMethodResolver MethodResolver
		{
			get { return resolver; }
		}

		public struct MethodImplementation
		{
			public StateTraitDOM<GlueTrait> trait;
			public MethodInfo method;
		}

		public MethodImplementation[] GetMethodImplementations(StateTraitDOM<GlueTrait> trait,
			MethodInfo declarationMethod)
		{
			IList<TraitMethodResolver.Implementation> impls = 
				resolver.GetMethodImplementations(declarationMethod);
			MethodImplementation[] ret = new MethodImplementation[impls.Count];
			for(int i = 0;i < impls.Count;i++)
			{
				ret[i].trait = (StateTraitDOM<GlueTrait>)impls[i].ImplementingTraitUserData;
				ret[i].method = impls[i].MethodInfo;
			}
			return ret;
		}

		public bool IsInitialState
		{
			get { return stateMachine.InitialState == this; }
			set
			{
				if (value)
					stateMachine.InitialState = this;
				else
					stateMachine.InitialState = null;
			}
		}

		internal void FireIsInitialStateChanged()
		{
			EventHandler evnt = IsInitialStateChanged;
			if (evnt != null)
				evnt(this, new EventArgs());
		}

		public event EventHandler IsInitialStateChanged;

		public class TraitCollection : DOMCollection<StateTraitDOM<GlueTrait>>
		{
			StateDOM<GlueTrait> state;

			internal TraitCollection(StateDOM<GlueTrait> state,IDOMNamespace ns)
				: base(ns)
			{
				this.state = state;
			}

			public StateTraitDOM<GlueTrait> Add(Type traitType)
			{
				return Add(NameSpace.CreateUniqueName(traitType.Name), traitType);
			}

			public StateTraitDOM<GlueTrait> Add(string name, Type traitType)
			{
				StateTraitDOM<GlueTrait> trait = 
					new StateTraitDOM<GlueTrait>(name, traitType);
				Add(trait);
				return trait;
			}

			public override StateTraitDOM<GlueTrait> Add(StateTraitDOM<GlueTrait> obj)
			{
				obj.SetState(state);
				return base.Add(obj);
			}
		}
	}

	public class TransitionsCollection<GlueTrait> : IList<StateTransitionDOM<GlueTrait>>
		where GlueTrait : StateGlueTrait
	{
		List<StateTransitionDOM<GlueTrait>> list = new List<StateTransitionDOM<GlueTrait>>();

		public event ItemAddedHandler<StateTransitionDOM<GlueTrait>> ItemAdded;
		public event ItemRemovedHandler<StateTransitionDOM<GlueTrait>> ItemRemoved;
		public event ItemNameChangedHandler<StateTransitionDOM<GlueTrait>> ItemNameChanged;

		internal void OnAdded(StateTransitionDOM<GlueTrait> trait)
		{
			int itemIndex = list.Count;
			list.Add(trait);

			ItemAddedHandler<StateTransitionDOM<GlueTrait>> handler = ItemAdded;
			if (handler != null)
			{
				handler(this, new ItemAddedArgs<StateTransitionDOM<GlueTrait>>(trait, itemIndex));
			}
		}

		internal void OnRemoved(int index)
		{
			ItemRemovedHandler<StateTransitionDOM<GlueTrait>> handler = ItemRemoved;
			if (handler != null)
			{
				handler(this, new ItemRemovedArgs<StateTransitionDOM<GlueTrait>>(list[index], index));
			}

			list.RemoveAt(index);
		}

		internal void OnNameChanged(StateTransitionDOM<GlueTrait> item, string oldName, string newName)
		{
			ItemNameChangedHandler<StateTransitionDOM<GlueTrait>> handler = ItemNameChanged;
			if(handler != null)
				handler(this,new ItemNameChangedArgs<StateTransitionDOM<GlueTrait>>(item,newName,oldName));
		}

		public int IndexOf(StateTransitionDOM<GlueTrait> item)
		{
			return list.IndexOf(item);
		}

		public StateTransitionDOM<GlueTrait> this[int index]
		{
			get { return list[index]; }
			set { Debug.Fail("This collection is read only"); }
		}

		public bool Contains(StateTransitionDOM<GlueTrait> item)
		{
			return list.Contains(item);
		}

		public void CopyTo(StateTransitionDOM<GlueTrait>[] array, int arrayIndex)
		{
			list.CopyTo(array,arrayIndex);
		}

		public bool IsReadOnly 
		{
			get { return true; } 
		}

		void IList<StateTransitionDOM<GlueTrait>>.Insert(int index, StateTransitionDOM<GlueTrait> item) { Debug.Fail("This collection is read only"); }
		void IList<StateTransitionDOM<GlueTrait>>.RemoveAt(int index) { Debug.Fail("This collection is read only"); }
		void ICollection<StateTransitionDOM<GlueTrait>>.Add(StateTransitionDOM<GlueTrait> item) { Debug.Fail("This collection is read only"); }
		public void Clear() { Debug.Fail("This collection is read only"); }
		public int Count { get { return list.Count; } }		
		public bool Remove(StateTransitionDOM<GlueTrait> item) { Debug.Fail("This collection is read only"); return false; }
		public IEnumerator<StateTransitionDOM<GlueTrait>> GetEnumerator() {	return list.GetEnumerator(); }
		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return list.GetEnumerator(); }
	}

	public class StateTraitDOM<GlueTrait> : DOMObj
		where GlueTrait : StateGlueTrait
	{
		Type type;
		StateDOM<GlueTrait> state;

		public StateTraitDOM(string name,Type type)
			: base(name)
		{
			this.type = type;
		}

		internal void SetState(StateDOM<GlueTrait> state)
		{
			this.state = state;
		}

		public ICollection<MethodInfo> ImplementedMethods
		{
			get
			{
				List<MethodInfo> ret = new List<MethodInfo>();
				foreach (MethodInfo method in type.GetMethods())
				{
					object[] attrs = method.GetCustomAttributes(
						typeof(TraitImplementMethodAttribute), true);
					if (attrs.Length != 0)
					{
						ret.Add(method);
					}
				}

				return ret;
			}
		}

		public ICollection<MethodInfo> DeclaredMethods
		{
			get
			{
				List<MethodInfo> ret = new List<MethodInfo>();
				foreach (MethodInfo method in type.GetMethods())
				{
					object[] attrs = method.GetCustomAttributes(
						typeof(TraitDeclareMethodAttribute), true);
					if (attrs.Length != 0)
					{
						ret.Add(method);
					}
				}

				return ret;
			}
		}

		public Type TraitType
		{
			get { return type; }
		}
	}

	public class StateTransitionDOM<GlueTrait> : StateTraitDOM<GlueTrait>
		where GlueTrait : StateGlueTrait
	{
		StateDOM<GlueTrait> from;
		StateDOM<GlueTrait> to;

		internal StateTransitionDOM(string name,Type type,
			StateDOM<GlueTrait> from,StateDOM<GlueTrait> to )
			: base(name,type)
		{
			Debug.Assert(from != null && to != null, "From and to shouldn't be null");
			Debug.Assert(type.IsSubclassOf(typeof(StateTransition<GlueTrait>)),
				"The transition type must be derived from StateTransition");

			this.from = from;
			this.to = to;
		}

		public StateDOM<GlueTrait> From
		{
			get { return from; }
		}

		public StateDOM<GlueTrait> To
		{
			get { return to; }
		}
	}
}